Skip to content

Conversation

@n-o-u-r-h-a-n
Copy link
Contributor

@n-o-u-r-h-a-n n-o-u-r-h-a-n commented Oct 27, 2025

Context

AI/ai-sdk-java-backlog#295.

Printing useful Logs for AI SDK. The following example shows a tool execution:

16:25:13.299 INFO  [main] c.s.a.s.c.AiCoreServiceKeyAccessor : Found a service key in environment variable AICORE_SERVICE_KEY.
Using a service key is recommended for local testing only.
Bind the AI Core service to the application for productive usage.

16:25:13.530 INFO  [cloudsdk-executor-0] c.s.c.s.client.HttpClientFactory : loaded HttpClientFactory service providers: [com.sap.cloud.security.client.DefaultHttpClientFactory@1f53c0b1]
16:25:13.554 INFO  [cloudsdk-executor-0] c.s.cloud.security.servlet.MDCHelper : Correlation id (key=correlation_id) was not found in the MDC, generating a new one: f467fa77-7344-4336-a8bf-51407532499f
16:25:14.229 INFO  [main] c.sap.ai.sdk.core.DeploymentResolver : Found 32 deployments in resource group 'default'
16:25:14.229 INFO  [main] c.s.a.s.c.AiCoreServiceKeyAccessor : Found a service key in environment variable AICORE_SERVICE_KEY.
Using a service key is recommended for local testing only.
Bind the AI Core service to the application for productive usage.

16:25:14.236 DEBUG [main] c.s.a.s.o.OrchestrationHttpExecutor : [reqId=e94aa17b] Starting Orchestration synchronous request to /v2/completion, destination=https://<some-desination-uri>
16:25:16.433 DEBUG [main] c.s.a.s.c.c.ClientResponseHandler : [reqId=e94aa17b] Orchestration request completed successfully with duration=2101ms, size=1,6KB.
16:25:16.606 DEBUG [main] c.s.a.s.o.s.OrchestrationChatModel : Executing 2 tool call(s) - [getCurrentWeather, getCurrentWeather]
16:25:16.613 DEBUG [main] c.s.a.s.o.s.OrchestrationChatModel : Re-invoking LLM with tool execution results.
16:25:16.662 INFO  [main] c.s.a.s.c.AiCoreServiceKeyAccessor : Found a service key in environment variable AICORE_SERVICE_KEY.
Using a service key is recommended for local testing only.
Bind the AI Core service to the application for productive usage.

16:25:16.665 DEBUG [main] c.s.a.s.o.OrchestrationHttpExecutor : [reqId=53f857a7] Starting Orchestration synchronous request to /v2/completion, destination=https://<some-desination-uri>
16:25:18.174 DEBUG [main] c.s.a.s.c.c.ClientResponseHandler : [reqId=53f857a7] Orchestration request completed successfully with duration=1478ms, size=1,9KB.

Feature scope:

Definition of Done

  • Functionality scope stated & covered
  • Tests cover the scope above
  • Error handling created / updated & covered by the tests above
  • Aligned changes with the JavaScript SDK
  • Documentation updated
  • Release notes updated

</root>

<!-- <logger name="org.apache.hc.client5.http.wire" level="DEBUG"/>-->
<!-- <logger name="org.apache.http.wire" level="DEBUG"/>-->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wire logs on unit tests finally

<root level="INFO"/>
<logger name="org.springframework.web" level="INFO"/>
<logger name="org.apache.hc.client5.http.wire" level="DEBUG"/>
<logger name="org.apache.http.wire" level="DEBUG"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logback.xml takes precedence

} else {
log.info("LLM request success");
}
log.debug("Response content:\n{}", content);
Copy link
Member

@rpanackal rpanackal Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Major)

I believe this needs to be removed. There was request from Joule on the same and we intentionally removed all request and response content logging in the past.

If there are no objections, I will remove this before the PR is merged

Copy link
Contributor

@newtork newtork Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreeing with @rpanackal

Also some side-rules:

  • For highly repetitive messages and/or huge message footprints, use trace instead of debug.
  • Do not use new-lines \n in log messages.

final T value = objectMapper.readValue(content, successType);
val timeHeaders = response.getHeaders("x-upstream-service-time");
if (timeHeaders.length > 0) {
log.info("LLM request success with duration:{}", timeHeaders[0].getValue());
Copy link
Contributor

@newtork newtork Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question/Minor)

  • Are all current and future messages to AI Core LLM requests? This is a generic/general response handling class 🤔

  • I wouldn't use "success", rather "completed". The user may have different understanding for "success".

  • Can we add HTTP response size to the message? e.g. 13KB
    response.getEntity().getContentLength()

Copy link
Member

@rpanackal rpanackal Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientResponseHandler is only invoked for openai and orchestration, and therefore "LLM requests" is not entirely wrong but ofc no guarantees for the future.

I have changed the request message to be more descriptive, including the response size.

Copy link
Contributor

@CharlesDuboisSAP CharlesDuboisSAP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

throw new OrchestrationClientException(
"Request to Orchestration service failed for " + path, e);
} finally {
MDC.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have 2 requests running simultaneously, could the shorter one clear the context of the longer one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MDC is thread local, so normally this shouldn't be a problem.

That said, we need to make sure that the MDC state is set and consumed per request level as the context is finally cleared after each request. A counter example would be setting the MDC in OrchestrationModuleConfig object and using it across multiple requests. Here the MDC context set in config creation is cleared on first request and cannot be expected to be available on subsequent requests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to point out: If you are ever outside of the thread, you will lose the MDC parameters.
It's not obvious when/whether that happens. For example, if we ever decide to use Resilience (or any other form of TimeLimiter), then the approach will stop working I think.

val sizeInfo = entityLength >= 0 ? String.format("%.1fKB", entityLength / 1024.0) : "unknown";
log.debug(
"[reqId={}] {} request completed successfully with duration={}, size={}.",
MDC.get("reqId"),
Copy link
Contributor

@newtork newtork Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Major/Question)

Why do you put MDC information here? They should be part of the general log pattern.
Edit: Normally these diagnostic context values are part of log patterns, instead of log messages. But I don't have a strong opinion against that.

What made you choose this String format with [...] and foo=bar? Are there any best practices that suggest that technical format? Do you expect tools to consume / evaluate the message?
Edit: We may have to define a so-called Architecture Decision Record to write down our decision what kind of pattern / format we prefer for variable based values.

Also please improve code format, this looks bad.
Edit: Have you considered using re-usable String constants? This would also help understanding these are dedicated diagnostic variables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first two question

Edit: Normally these diagnostic context values are part of log patterns, instead of log messages.

My initial approach was applying the MDC on log patterns. But this lead to having awkward atifacts in all logs because there variable are not defined/expected to be available for them. Conditional patterns made the pattern itself unreadable and difficult to apply symbols to easily discern parts of the log.

What made you choose this String format with [...] and foo=bar? Are there any best practices that suggest that technical format? Do you expect tools to consume / evaluate the message?

I do not expect them to be machine readable. But purely thought of adopting a log format this is extendable incase we want to log additional information eg: "... status=OK". We choose purely descriptive messages instead. No strong opinion.

Have you considered using re-usable String constants? This would also help understanding these are dedicated diagnostic variables.

We can definitely add String constants.

newtork
newtork previously approved these changes Oct 30, 2025
newtork
newtork previously approved these changes Oct 30, 2025
@rpanackal rpanackal merged commit bb64261 into main Oct 30, 2025
7 checks passed
@rpanackal rpanackal deleted the Improve-Logging branch October 30, 2025 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants